Skip to content

refactor(errors): route git-plumbing failure bails through CommandError#3474

Merged
max-sixty merged 2 commits into
mainfrom
normalize-stdout-cr
Jul 15, 2026
Merged

refactor(errors): route git-plumbing failure bails through CommandError#3474
max-sixty merged 2 commits into
mainfrom
normalize-stdout-cr

Conversation

@max-sixty

Copy link
Copy Markdown
Owner

Follow-up to #3459. Eight git-plumbing sites turned a captured non-zero exit into bail!("…{stderr}"), bypassing CommandError::from_failed_output — the one constructor that normalizes \r and gives renderers the typed command/output split. This converts them (resolve_git_common_dir, the bulk config --list read, config --unset, config --get-regexp, merge-tree, merge-base, the untracked-diff numstat, and llm.rs::run_git_capture), so failed-command display routes through one constructor and the main.rs multiline-error tripwire has nothing left to catch on these paths. from_failed_output now takes &[impl AsRef<str>] so Vec<String> arg lists work.

One structural fix rode along: generate_commit_message now wraps only the execute_llm_command failure in GitError::LlmCommandFailed, mirroring generate_squash_message — a git failure while building the prompt is not an LLM-command failure, and flattening it into LlmCommandFailed.error dropped the typed stderr.

Rendering: context-wrapped failures render byte-identically (the outside-repo snapshots pass untouched); bare ones gain the standard git … failed (exit N) header + gutter. The only snapshot changes are two wt list task-failure lines where merge-base errors drop the hand-rolled git merge-base failed for <sha> <sha>: prefix and now match their sibling lines.

Every converted error path has a direct test — five new ones using empirically-probed failure modes (invalid regexp pattern → exit 6, invalid key → exit 1, corrupt config → 128 for the lazily-populated bulk read and merge-tree, unborn HEAD for the numstat), plus the updated run_git_capture unit test; resolve_git_common_dir and merge_base were already covered by existing snapshots.

Two same-pattern sites remain in the commands layer (step/shared.rs ls-files, step/commit.rs stage-to-temp-index) — deliberately left for a follow-up along with the non-git CLIs (gh/glab, claude/codex).

This was written by Claude Code on behalf of max

Eight sites turned a captured non-zero exit into bail!("…{stderr}"),
bypassing CommandError::from_failed_output — the one constructor that
normalizes \r and gives renderers the typed command/output split. Convert
them: resolve_git_common_dir, the bulk config --list read, config --unset,
config --get-regexp, merge-tree, merge-base, the untracked-diff numstat,
and llm.rs run_git_capture (which now builds the Cmd itself so it can name
the args). from_failed_output takes &[impl AsRef<str>] so Vec<String>
arg lists work.

generate_commit_message now wraps only the execute_llm_command failure in
LlmCommandFailed, mirroring generate_squash_message — a git failure while
building the prompt is not an LLM-command failure, and flattening it into
LlmCommandFailed.error dropped the typed stderr.

Rendering: wrapped failures (context + CommandError) render identically;
bare ones gain the standard 'git … failed (exit N)' header + gutter. The
wt list task-failure lines for merge-base errors drop the hand-rolled
'git merge-base failed for <sha> <sha>:' prefix and now match their
sibling lines (bare stderr), per the updated snapshots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, behavior-preserving refactor — every converted path now routes failed-command display through the single from_failed_output constructor, and the direct-test coverage on each is thorough. One optional micro-nit inline; nothing blocking.

Comment thread src/git/repository/working_tree.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@max-sixty max-sixty merged commit 489fd0f into main Jul 15, 2026
34 of 35 checks passed
@max-sixty max-sixty deleted the normalize-stdout-cr branch July 15, 2026 05:42
max-sixty added a commit that referenced this pull request Jul 15, 2026
…dError (#3483)

Follow-up to #3474: converts the remaining raw-output
`bail!("{stderr}")` sites to typed `CommandError`, so every
failed-command display goes through the one normalizing constructor
(`\r` stripping, canonical `✗ <command> failed (exit N)` header + output
gutter).

**Sites converted:**

- `step/shared.rs` (`git ls-files` in ignored-entry listing) and
`step/commit.rs` (`git add` into the temp index) — the two
commands-layer git sites.
- The five claude/codex plugin CLI calls in
`commands/config/{plugins,codex}.rs`, collapsed into one private
`run_plugin_cli` helper in `commands/config/mod.rs`. Their failure
snapshots now show the canonical header + stderr gutter instead of `✗
claude plugin marketplace add failed: <stderr>`.
- `execute_llm_command` in `llm.rs` — its hand-rolled
stderr/stdout/exit-code fallback ladder was `from_failed_output`'s job;
deleted in favor of the constructor. The `LlmCommandFailed` wrappers
read the detail via `ErrorExt::display_message()`, which extracts the
captured output for command failures and matches the old text in the
common cases; the picker summary pane guards on the error type
(`format_summary_error`) — command failures surface their captured
output, everything else keeps the full anyhow chain the pane always
showed (folded in from review). The shell program is recorded by
basename only (same rule as `render_llm_invocation`), so a no-output
failure doesn't leak the Git Bash install path on Windows.

**Deliberately left:** the two `gh pr list` / `glab mr list` bails in
`picker/prs.rs`. Their consumer is the skim-footer warning sink where
the current one-line text is the intended display, and picker text
sanitization was explicitly dropped (#3177).

**Behavior note** (intended, but visible): an LLM command that emits on
both streams before failing now shows both in the error block
(`combined_output()` joins stderr + stdout); the old ladder showed
stderr only.

**Testing:** three new error-path unit tests (`list_ignored_entries` /
`stage_to_temp_index` via corrupt-config and bad-pathspec,
`execute_llm_command` exit-code + basename assertion); the five plugin
sites are covered by the existing failing-mock integration tests whose
snapshots this updates; `test_generate_summary_llm_error` covers the
summary-pane command-failure arm and
`format_summary_error_keeps_chain_for_non_command_errors` the
chain-preserving arm.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
max-sixty added a commit that referenced this pull request Jul 15, 2026
)

Step-12 drift fold for the v0.68.0 release: #3483 (the follow-up
completing #3474) merged during the release PR CI window, so it ships in
0.68.0 but the changelog entry only covered the eight #3474 sites. The
entry now covers the full cutover and cites #3483.

> _This was written by Claude Code on behalf of max_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants